---
id: dbcontext-function
title: 9.14 函数操作
sidebar_label: 9.14 函数操作
---

:::important 温馨提示

推荐使用 《[9.15 Sql 高级代理](dbcontext-sql-proxy.mdx)》代替本章节功能。`Sql 高级代理` 能够提供更容易且更易维护的方式。

:::

import useBaseUrl from "@docusaurus/useBaseUrl";

## 9.14.1 数据库函数

引用百度百科：

> 数据库函数是指当需要分析数据清单中的数值是否符合特定条件时，使用数据库工作表函数。

简单来说，数据库函数就是用于子计算的函数。其计算的结果可以用于构建 `sql` 语句。

### 9.14.1.1 支持标量函数的数据库

| SqlServer | Sqlite | Cosmos | InMemoryDatabase | MySql | PostgreSQL | Oracle | Firebird | Dm  |
| --------- | ------ | ------ | ---------------- | ----- | ---------- | ------ | -------- | --- |
| ✔         |        | ✔      |                  | ✔     | ✔          | ✔      |          |     |

### 9.14.1.2 支持表值函数的数据库

| SqlServer | Sqlite | Cosmos | InMemoryDatabase | MySql | PostgreSQL | Oracle | Firebird | Dm  |
| --------- | ------ | ------ | ---------------- | ----- | ---------- | ------ | -------- | --- |
| ✔         |        | ✔      |                  |       | ✔          | ✔      |          |     |

## 9.14.2 数据库函数类型

在关系型数据库中，数据库函数有这两种类型：

- `标量函数`：只能返回单个值
- `表值函数`：只能返回一个结果集

## 9.14.3 函数的使用

### 9.14.3.1 标量函数返回 `object`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlFunctionScalar("func_GetValue");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetValue();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlFunctionScalar("func_GetValue");

// IRepository 非泛型方式
var value = _repository.Sql().SqlFunctionScalar("func_GetValue");

// 变态懒人方式，直接通过函数名执行
var value = "func_GetValue".SqlFunctionScalar();
```

:::note 关于异步

`Fur` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.14.3.2 标量函数返回 `T`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlFunctionScalar<string>("func_GetValue");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetValue();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlFunctionScalar<string>("func_GetValue");

// IRepository 非泛型方式
var value = _repository.Sql().SqlFunctionScalar<string>("func_GetValue");

// 变态懒人方式，直接通过函数名执行
var value = "func_GetValue".SqlFunctionScalar<string>();
```

:::note 关于异步

`Fur` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.14.3.3 表值函数返回 `DataTable`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlFunctionQuery("func_GetTable");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetTable();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlFunctionQuery("func_GetTable");

// IRepository 非泛型方式
var value = _repository.Sql().SqlFunctionQuery("func_GetTable");

// 变态懒人方式，直接通过函数名执行
var value = "func_GetTable".SqlFunctionQuery();
```

:::note 关于异步

`Fur` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.14.3.4 表值函数返回 `List<T>`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlFunctionQuery<Person>("func_GetTable");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetTable();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlFunctionQuery<Person>("func_GetTable");

// IRepository 非泛型方式
var value = _repository.Sql().SqlFunctionQuery<Person>("func_GetTable");

// 变态懒人方式，直接通过函数名执行
var value = "func_GetTable".SqlFunctionQuery<Person>();
```

:::note 关于异步

`Fur` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

## 9.14.4 在 `Linq` 中使用 `标量函数`

`Fur` 框架提供非常灵活的在 `Linq` 中使用标量函数的方法。如果像使用这样的方式，需要满足以下两个条件：

- 标量函数必须定义在**公开静态类**中，且自己也是**公开静态方法**
- 该**公开静态方法**必须贴有 `[QueryableFunction]` 特性

示例如下：

### 9.14.4.1 创建标量函数

```sql
CREATE FUNCTION FN_GetId
(
    @id INT
)
RETURNS INT
AS
BEGIN
    RETURN @id + 1;
END;
```

### 9.14.4.2 创建静态类和静态方法

创建静态类，如 `QueryFunctions`，将该 `标量函数` 放在静态类中：

```cs {1, 7, 10-11}
using Fur.DatabaseAccessor;
using System;

namespace Fur.Application
{
    // 必须是公开静态的
    public static class QueryFunctions
    {
        // 必须是静态方法
        [QueryableFunction("FN_GetId", "dbo")]  // 配置标量函数
        public static int GetId(int id) => throw new NotSupportedException();
    }
}
```

### 9.14.4.3 在 `Linq` 中使用

```cs
_personRepository.Where(u => u.Id > QueryFunctions.GetId(1)).ToList();
```

```sql
SELECT [p].[Id], [p].[Address], [p].[Age], [p].[CreatedTime], [p].[IsDeleted], [p].[Name], [p].[UpdatedTime]
FROM [Person] AS [p]
WHERE [p].[Id] > [dbo].[FN_GetId](1)    // 💥 注意这里
```

<img src={useBaseUrl("img/fn1.png")} />

## 9.14.5 在 `Linq` 中使用 `表值函数`

`EF Core 5.0` 版本支持在 `Linq` 中操作 `表值函数`，操作有点类似 `视图操作`

示例如下：

### 9.14.5.1 创建表值函数

```sql
CREATE FUNCTION dbo.GetPersons
(
    @id INT
)
RETURNS TABLE
AS
RETURN
(
    SELECT Id,
           Name,
           Age,
           Address
    FROM dbo.Person
    WHERE Id > @id
);
```

### 9.14.5.2 创建表值函数模型

```cs
namespace Fur.Core
{
    public class F_Person
    {
        /// <summary>
        /// 主键Id
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// 住址
        /// </summary>
        public string Address { get; set; }
    }
}
```

### 9.14.5.3 表值函数配置

在 `DbContext` 类中定义方法：

```cs {3,10,20-21}
using Fur.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace Fur.EntityFramework.Core
{
    [AppDbContext("Sqlite3ConnectionString")]
    public class FurDbContext : AppDbContext<FurDbContext>
    {
        public IQueryable<F_Person> GetPersons(int id) => FromExpression(() => GetPersons(id));

        public FurDbContext(DbContextOptions<FurDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity(typeof(F_Person)).HasNoKey();
            modelBuilder.HasDbFunction(() => GetPersons(default));
        }
    }
}
```

### 9.14.5.4 在 `Linq` 中使用

```cs
IQueryable<F_Person> query = _repository.DynamicDbContext.GetPersons(1);
var result = query.Where(u => u.Name.Equals("Fur")).ToList();
```

最终生成 `Sql`

```sql
SELECT [g].Id, [g].Name, [g].Age, [g].Address
FROM dbo.GetPersons(1) AS [g]
WHERE [g].Name == N'Fur';
```

## 9.14.6 在 `EF Core` 内置函数

`EF Core` 为我们提供了很多常用的内置函数，可以在 `Lambda` 条件中使用，主要是通过 EF.Functions 调用，如：

```cs
_repository.Where(u => EF.Functions.DateDiffHour(u.CreatedDt, DateTime.Now) > 8).FirstOrDefault();
```

这个语句使用了 EF.Functions.DateDiffHour 最终生成的 Sql 如下：

```sql
SELECT TOP(1) [a].*
FROM [dbo].[TEST] AS [a]
WHERE DATEDIFF(HOUR, [a].[CREATED_DT], GETDATE()) > 8
```

`EF Core` 内置函数就不一一列出了，可以通过 `EF.Functions` 查看更多，如果不能满足自己的需求，那么可以自定义 `Linq` 标量函数

## 9.14.7 反馈与建议

:::note 与我们交流

给 Fur 提 [Issue](https://gitee.com/monksoul/Fur/issues/new?issue)。

:::
